trait BaseExpr {
// would've liked: type TExpr[t] <: ExprBase[t]
    type TExpr <: ExprBase
    type TNum <: NumBase with TExpr
    type TPlus <: PlusBase with TExpr
    type TEq <: EqBase with TExpr
    type TIf <: IfBase with TExpr
    
    abstract class ExprBase requires TExpr {
        type t
        
        def eval :t
    }
    
      trait NumBase requires TNum extends ExprBase {
        type t = Int
        val n :Int
        
        def eval :t = n
      }
      
      trait PlusBase requires TPlus extends ExprBase {
        type t = Int
        val l :TExpr{type t=Int}
        val r :TExpr{type t=Int}
        
        def eval :t = l.eval + r.eval
      }
      
      trait EqBase requires TEq  extends ExprBase {
        type t = Boolean
        val l :TExpr{type t=Int}
        val r :TExpr{type t=Int}
        
        def eval :t = l.eval == r.eval
      }

      //type TIf[a] <: IfBase[a] with TExpr
      //case class IfBase[a](c :TExpr{type t=Boolean}, t :TExpr{type t=a}, f :TExpr{type t=a})  requires TIf  extends ExprBase {
        //    type t = a
        
      trait IfBase  requires TIf  extends ExprBase {
        val c :TExpr{type t=Boolean}
        val bTrue :TExpr{type t=IfBase.this.t}
        val bFalse :TExpr{type t=IfBase.this.t}
    
        def eval :t = if(c.eval) bTrue.eval else bFalse.eval
      }
        
}

      
trait StringExpr extends BaseExpr {
  type TString <: StringBase with TExpr
  
  trait StringBase requires TString extends ExprBase {
      type t = String
      val s :String
      
      def eval :t = s
  }
}

trait ShowExpr extends BaseExpr {
  type TExpr <: ExprBase with ExprShow
  
    trait ExprShow requires TExpr extends ExprBase {
        def show :String = toString() + " = " + eval
    }
}

object main extends BaseExpr with ShowExpr with StringExpr {
  type TExpr = Expr 
  type TNum = Num
  type TPlus = Plus
  type TEq = Eq
  type TIf = Expr with IfBase
  type TString = Str
  
  abstract class Expr extends ExprBase with ExprShow 
    case class Num(num :Int) extends Expr with NumBase {val n=num} 
    case class Plus(left :TExpr{type t=Int}, right :TExpr{type t=Int}) extends Expr with PlusBase  {val l=left;val r=right}
    case class Eq(left :TExpr{type t=Int}, right :TExpr{type t=Int})  extends Expr with EqBase  {val l=left;val r=right}
    case class If[a](cond :TExpr{type t=Boolean}, branchTrue :TExpr{type t=a}, branchFalse :TExpr{type t=a}) 
                extends Expr with IfBase {type t=a;val c=cond; val bTrue=branchTrue; val bFalse = branchFalse}
    case class Str(str :String)  extends Expr with StringBase {val s=str}
   
  def main(args :Array[String]) :Unit = {    
    val x = If ( Eq(Num(1), Num(2)), Str("one==two!"), Str("phew..."))
    x.eval
    System.out.println(x.show )
  }
}

object maingadt {
  abstract class Expr[t] 
    case class Num(num :Int) extends Expr[Int]
    case class Plus(left :Expr[Int], right :Expr[Int]) extends Expr[Int]
    case class Eq(left :Expr[Int], right :Expr[Int])  extends Expr[Boolean]
    case class If[a](c :Expr[Boolean], t :Expr[a], f :Expr[a]) extends Expr[a]
    case class Str(str :String)  extends Expr[String]
    
  def evalPM[t](expr :Expr[t]) :t = expr match {
    case Num(n) => n
    case Plus(l,r) => evalPM(l) + evalPM(r)
    case Eq(l,r) => evalPM(l) == evalPM(r)
    case If(c, t, f) => if(evalPM(c)) evalPM(t) else evalPM(f)
    case Str(s) => s
  }
  
  def main(args :Array[String]) :Unit = {    
    val x = If ( Eq(Num(1), Num(2)), Str("one==two!"), Str("phew..."))
    
    val s :String = evalPM(x)
    
    System.out.println(x.toString + " = " + s)
  }
}

/*
  type TExpr = ExprBase with ExprShow 
  type TNum = Num
  type TPlus = Plus
  type TEq = Eq
  type TIf = ExprBase with ExprShow with IfBase
  type TString = StringExpr
  
  abstract class Expr[a] extends ExprBase with ExprShow { type t=a}
    case class Num(num :Int) extends Expr[Int] with NumBase {override type t=Int; val n=num} 
    case class Plus(left :TExpr{type t=Int}, right :TExpr{type t=Int}) extends Expr[Int] with PlusBase  {override type t=Int;val l=left;val r=right}
    case class Eq(left :TExpr{type t=Int}, right :TExpr{type t=Int})  extends Expr[Boolean] with EqBase  {override type t=Boolean;val l=left;val r=right}
    case class If[a](cond :TExpr{type t=Boolean}, branchTrue :TExpr{type t=a}, branchFalse :TExpr{type t=a}) 
                extends Expr[a] with IfBase {val c=cond; val bTrue=branchTrue; val bFalse = branchFalse}
    case class StringExpr(str :String)  extends Expr[String] with StringBase {override type t=String;val s=str}
   
  def evalPM[ty](expr :Expr[ty]) :ty = expr match {
    case Num(n) => n
    case Plus(l,r) => evalPM[Int](l) + evalPM(r)
    case Eq(l,r) => evalPM(l) == evalPM(r)
    case If(c, t, f) => if(evalPM(c)) evalPM(t) else evalPM(f)
    case StringExpr(s) => s
  }

  */

